<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_2</span></h2>

  <p>较，而不是仅仅面向一个，这时又该怎么办呢？</p>

  <p>办法就是使用一个“继续器”（Iterator），它属于一种对象，负责选择集合内的元素，并把它们提供给继承器的用户。作为一个类，它也提供了一级抽象。利用这一级抽象，可将集合细节与用于访问那个集合的代码隔离开。通过继承器的作用，集合被抽象成一个简单的序列。继承器允许我们遍历那个序列，同时毋需关心基础结构是什么――换言之，不管它是一个矢量、一个链接列表、一个堆栈，还是其他什么东西。这样一来，我们就可以灵活地改变基础数据，不会对程序里的代码造成干扰。Java最开始（在1.0和1.1版中）提供的是一个标准继承器，名为Enumeration（枚举），为它的所有集合类提供服务。Java 1.2新增一个更复杂的集合库，其中包含了一个名为Iterator的继承器，可以做比老式的Enumeration更多的事情。</p>

  <p>从设计角度出发，我们需要的是一个全功能的序列。通过对它的操纵，应该能解决自己的问题。如果一种类型的序列即可满足我们的所有要求，那么完全没有必要再换用不同的类型。有两方面的原因促使我们需要对集合作出选择。首先，集合提供了不同的接口类型以及外部行为。堆栈的接口与行为与队列的不同，而队列的接口与行为又与一个集（Set）或列表的不同。利用这个特征，我们解决问题时便有更大的灵活性。</p>

  <p>其次，不同的集合在进行特定操作时往往有不同的效率。最好的例子便是矢量（Vector）和列表（List）的区别。它们都属于简单的序列，拥有完全一致的接口和外部行为。但在执行一些特定的任务时，需要的开销却是完全不同的。对矢量内的元素进行的随机访问（存取）是一种常时操作；无论我们选择的选择是什么，需要的时间量都是相同的。但在一个链接列表中，若想到处移动，并随机挑选一个元素，就需付出“惨重”的代价。而且假设某个元素位于列表较远的地方，找到它所需的时间也会长许多。但在另一方面，如果想在序列中部插入一个元素，用列表就比用矢量划算得多。这些以及其他操作都有不同的执行效率，具体取决于序列的基础结构是什么。在设计阶段，我们可以先从一个列表开始。最后调整性能的时候，再根据情况把它换成矢量。由于抽象是通过继承器进行的，所以能在两者方便地切换，对代码的影响则显得微不足道。</p>

  <p>最后，记住集合只是一个用来放置对象的储藏所。如果那个储藏所能满足我们的所有需要，就完全没必要关心它具体是如何实现的（这是大多数类型对象的一个基本概念）。如果在一个编程环境中工作，它由于其他因素（比如在Windows下运行，或者由垃圾收集器带来了开销）产生了内在的开销，那么矢量和链接列表之间在系统开销上的差异就或许不是一个大问题。我们可能只需要一种类型的序列。甚至可以想象有一个“完美”的集合抽象，它能根据自己的使用方式自动改变基层的实现方式。</p>

  <p>1.7.2 单根结构</p>

  <p>在面向对象的程序设计中，由于C++的引入而显得尤为突出的一个问题是：所有类最终是否都应从单独一个基础类继承。在Java中（与其他几乎所有OOP语言一样），对这个问题的答案都是肯定的，而且这个终级基础类的名字很简单，就是一个“Object”。这种“单根结构”具有许多方面的优点。</p>

  <p>单根结构中的所有对象都有一个通用接口，所以它们最终都属于相同的类型。另一种方案（就象C++那样）是我们不能保证所有东西都属于相同的基本类型。从向后兼容的角度看，这一方案可与C模型更好地配合，而且可以认为它的限制更少一些。但假期我们想进行纯粹的面向对象编程，那么必须构建自己的结构，以期获得与内建到其他OOP语言里的同样的便利。需添加我们要用到的各种新类库，还要使用另一些不兼容的接口。理所当然地，这也需要付出额外的精力使新接口与自己的设计方案配合（可能还需要多重继承）。为得到C++额外的“灵活性”，付出这样的代价值得吗？当然，如果真的需要――如果早已是C专家，如果对C有难舍的情结――那么就真的很值得。但假如你是一名新手，首次接触这类设计，象Java那样的替换方案也许会更省事一些。</p>

  <p>单根结构中的所有对象（比如所有Java对象）都可以保证拥有一些特定的功能。在自己的系统中，我们知道对每个对象都能进行一些基本操作。一个单根结构，加上所有对象都在内存堆中创建，可以极大简化参数的传递（这在C++里是一个复杂的概念）。</p>

  <p>利用单根结构，我们可以更方便地实现一个垃圾收集器。与此有关的必要支持可安装于基础类中，而垃圾收集器可将适当的消息发给系统内的任何对象。如果没有这种单根结构，而且系统通过一个句柄来操纵对象，那么实现垃圾收集器的途径会有很大的不同，而且会面临许多障碍。</p>

  <p>由于运行期的类型信息肯定存在于所有对象中，所以永远不会遇到判断不出一个对象的类型的情况。这对系统级的操作来说显得特别重要，比如违例控制；而且也能在程序设计时获得更大的灵活性。</p>

  <p>但大家也可能产生疑问，既然你把好处说得这么天花乱坠，为什么C++没有采用单根结构呢？事实上，这是早期在效率与控制上权衡的一种结果。单根结构会带来程序设计上的一些限制。而且更重要的是，它加大了新程序与原有C代码兼容的难度。尽管这些限制仅在特定的场合会真的造成问题，但为了获得最大的灵活程度，C++最终决定放弃采用单根结构这一做法。而Java不存在上述的问题，它是全新设计的一种语言，不必与现有的语言保持所谓的“向后兼容”。所以很自然地，与其他大多数面向对象的程序设计语言一样，单根结构在Java的设计方案中很快就落实下来。</p>

  <p>1.7.3 集合库与方便使用集合</p>

  <p>由于集合是我们经常都要用到的一种工具，所以一个集合库是十分必要的，它应该可以方便地重复使用。这样一来，我们就可以方便地取用各种集合，将其插入自己的程序。Java提供了这样的一个库，尽管它在Java 1.0和1.1中都显得非常有限（Java 1.2的集合库则无疑是一个杰作）。</p>

  <p>1. 下溯造型与模板／通用性</p>

  <p>为了使这些集合能够重复使用，或者“再生”，Java提供了一种通用类型，以前曾把它叫作“Object”。单根结构意味着、所有东西归根结底都是一个对象”！所以容纳了Object的一个集合实际可以容纳任何东西。这使我们对它的重复使用变得非常简便。</p>

  <p>为使用这样的一个集合，只需添加指向它的对象句柄即可，以后可以通过句柄重新使用对象。但由于集合只能容纳Object，所以在我们向集合里添加对象句柄时，它会上溯造型成Object，这样便丢失了它的身份或者标识信息。再次使用它的时候，会得到一个Object句柄，而非指向我们早先置入的那个类型的句柄。所以怎样才能归还它的本来面貌，调用早先置入集合的那个对象的有用接口呢？</p>

  <p>在这里，我们再次用到了造型（Cast）。但这一次不是在分级结构中上溯造型成一种更“通用”的类型。而是下溯造型成一种更“特殊”的类型。这种造型方法叫作“下溯造型”（Downcasting）。举个例子来说，我们知道在上溯造型的时候，Circle（圆）属于Shape（几何形状）的一种类型，所以上溯造型是安全的。但我们不知道一个Object到底是Circle还是Shape，所以很难保证下溯造型的安全进行，除非确切地知道自己要操作的是什么。</p>

  <p>但这也不是绝对危险的，因为假如下溯造型成错误的东西，会得到我们称为“违例”（Exception）的一种运行期错误。我们稍后即会对此进行解释。但在从一个集合提取对象句柄时，必须用某种方式准确地记住它们是什么，以保证下溯造型的正确进行。</p>

  <p>下溯造型和运行期检查都要求花额外的时间来运行程序，而且程序员必须付出额外的精力。既然如此，我们能不能创建一个“智能”集合，令其知道自己容纳的类型呢？这样做可消除下溯造型的必要以及潜在的错误。答案是肯定的，我们可以采用“参数化类型”，它们是编译器能自动定制的类，可与特定的类型配合。例如，通过使用一个参数化集合，编译器可对那个集合进行定制，使其只接受Shape，而且只提取Shape。</p>

  <p>参数化类型是C++一个重要的组成部分，这部分是C++没有单根结构的缘故。在C++中，用于实现参数化类型的关键字是template（模板）。Java目前尚未提供参数化类型，因为由于使用的是单根结构，所以使用它显得有些笨拙。但这并不能保证以后的版本不会实现，因为“generic”这个词已被Java“保留到将来实现”（在Ada语言中，“generic”被用来实现它的模板）。Java采取的这种关键字保留机制其实经常让人摸不着头脑，很难断定以后会发生什么事情。</p>

  <p>1.7.4 清除时的困境：由谁负责清除？</p>

  <p>每个对象都要求资源才能“生存”，其中最令人注目的资源是内存。如果不再需要使用一个对象，就必须将其清除，以便释放这些资源，以便其他对象使用。如果要解决的是非常简单的问题，如何清除对象这个问题并不显得很突出：我们创建对象，在需要的时候调用它，然后将其清除或者“破坏”。但在另一方面，我们平时遇到的问题往往要比这复杂得多。</p>

  <p>举个例子来说，假设我们要设计一套系统，用它管理一个机场的空中交通（同样的模型也可能适于管理一个仓库的货柜、或者一套影带出租系统、或者宠物店的宠物房。这初看似乎十分简单：构造一个集合用来容纳飞机，然后创建一架新飞机，将其置入集合。对进入空中交通管制区的所有飞机都如此处理。至于清除，在一架飞机离开这个区域的时候把它简单地删去即可。</p>

  <p>但事情并没有这么简单，可能还需要另一套系统来记录与飞机有关的数据。当然，和控制器的主要功能不同，这些数据的重要性可能一开始并不显露出来。例如，这条记录反映的可能是离开机场的所有小飞机的飞行计划。所以我们得到了由小飞机组成的另一个集合。一旦创建了一个飞机对象，如果它是一架小飞机，那么也必须把它置入这个集合。然后在系统空闲时期，需对这个集合中的对象进行一些后台处理。</p>

  <p>问题现在显得更复杂了：如何才能知道什么时间删除对象呢？用完对象后，系统的其他某些部分可能仍然要发挥作用。同样的问题也会在其他大量场合出现，而且在程序设计系统中（如C++），在用完一个对象之后必须明确地将其删除，所以问题会变得异常复杂（注释⑥）。</p>

  <p>⑥：注意这一点只对内存堆里创建的对象成立（用new命令创建的）。但在另一方面，对这儿描述的问题以及其他所有常见的编程问题来说，都要求对象在内存堆里创建。</p>

  <p>在Java中，垃圾收集器在设计时已考虑到了内存的释放问题（尽管这并不包括清除一个对象涉及到的其他方面）。垃圾收集器“知道”一个对象在什么时候不再使用，然后会自动释放那个对象占据的内存空间。采用这种方式，另外加上所有对象都从单个根类Object继承的事实，而且由于我们只能在内存堆中以一种方式创建对象，所以Java的编程要比C++的编程简单得多。我们只需要作出少量的抉择，即可克服原先存在的大量障碍。</p>

  <p>1. 垃圾收集器对效率及灵活性的影响</p>

  <p>既然这是如此好的一种手段，为什么在C++里没有得到充分的发挥呢？我们当然要为这种编程的方便性付出一定的代价，代价就是运行期的开销。正如早先提到的那样，在C++中，我们可在堆栈中创建对象。在这种情况下，对象会得以自动清除（但不具有在运行期间随心所欲创建对象的灵活性）。在堆栈中创建对象是为对象分配存储空间最有效的一种方式，也是释放那些空间最有效的一种方式。在内存堆（Heap）中创建对象可能要付出昂贵得多的代价。如果总是从同一个基础类继承，并使所有函数调用都具有“同质多形”特征，那么也不可避免地需要付出一定的代价。但垃圾收集器是一种特殊的问题，因为我们永远不能确定它什么时候启动或者要花多长的时间。这意味着在Java程序执行期间，存在着一种不连贯的因素。所以在某些特殊的场合，我们必须避免用它――比如在一个程序的执行必须保持稳定、连贯的时候（通常把它们叫作“实时程序”，尽管并不是所有实时编程问题都要这方面的要求――注释⑦）。</p>

  <p>⑦：根据本书一些技术性读者的反馈，有一个现成的实时Java系统（www.newmonics.com）确实能够保证垃圾收集器的效能。</p>

  <p>C++语言的设计者曾经向C程序员发出请求（而且做得非常成功），不要希望在可以使用C的任何地方，向语言里加入可能对C++的速度或使用造成影响的任何特性。这个目的达到了，但代价就是C++的编程不可避免地复杂起来。Java比C++简单，但付出的代价是效率以及一定程度的灵活性。但对大多数程序设计问题来说，Java无疑都应是我们的首选。</p>

  <p>1.8 违例控制：解决错误</p>

  <p>从最古老的程序设计语言开始，错误控制一直都是设计者们需要解决的一个大问题。由于很难设计出一套完美的错误控制方案，许多语言干脆将问题简单地忽略掉，将其转嫁给库设计人员。对大多数错误控制方案来说，最主要的一个问题是它们严重依赖程序员的警觉性，而不是依赖语言本身的强制标准。如果程序员不够警惕――若比较匆忙，这几乎是肯定会发生的――程序所依赖的错误控制方案便会失效。</p>

  <p>“违例控制”将错误控制方案内置到程序设计语言中，有时甚至内建到操作系统内。这里的“违例”（Exception）属于一个特殊的对象，它会从产生错误的地方“扔”或“掷”出来。随后，这个违例会被设计用于控制特定类型错误的“违例控制器”捕获。在情况变得不对劲的时候，可能有几个违例控制器并行捕获对应的违例对象。由于采用的是独立的执行路径，所以不会干扰我们的常规执行代码。这样便使代码的编写变得更加简单，因为不必经常性强制检查代码。除此以外，“掷”出的一个违例不同于从函数返回的错误值，也不同于由函数设置的一个标志。那些错误值或标志的作用是指示一个错误状态，是可以忽略的。但违例不能被忽略，所以肯定能在某个地方得到处置。最后，利用违例能够可靠地从一个糟糕的环境中恢复。此时一般不需要退出，我们可以采取某些处理，恢复程序的正常执行。显然，这样编制出来的程序显得更加可靠。</p>

  <p>Java的违例控制机制与大多数程序设计语言都有所不同。因为在Java中，违例控制模块是从一开始就封装好的，所以必须使用它！如果没有自己写一些代码来正确地控制违例，就会得到一条编译期出错提示。这样可保证程序的连贯性，使错误控制变得更加容易。</p>

  <p>注意违例控制并不属于一种面向对象的特性，尽管在面向对象的程序设计语言中，违例通常是用一个对象表示的。早在面向对象语言问世以前，违例控制就已经存在了。</p>

  <p>1.9 多线程</p>

  <p>在计算机编程中，一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手头的工作，改为处理其他一些问题，再返回主进程。可以通过多种途径达到这个目的。最开始的时候，那些拥有机器低级知识的程序员编写一些“中断服务例程”，主进程的暂停是通过硬件级的中断实现的。尽管这是一种有用的方法，但编出的程序很难移植，由此造成了另一类的代价高昂问题。</p>

  <p>有些时候，中断对那些实时性很强的任务来说是很有必要的。但还存在其他许多问题，它们只要求将问题划分进入独立运行的程序片断中，使整个程序能更迅速地响应用户的请求。在一个程序中，这些独立运行的片断叫作“线程”（Thread），利用它编程的概念就叫作“多线程处理”。多线程处理一个常见的例子就是用户界面。利用线程，用户可按下一个按钮，然后程序会立即作出响应，而不是让用户等待程序完成了当前任务以后才开始响应。</p>

  <p>最开始，线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器，那么每个线程都可分配给一个不同的处理器，真正进入“并行运算”状态。从程序设计语言的角度看，多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程；假如机器本身安装了多个处理器，那么程序会运行得更快，毋需作出任何特殊的调校。</p>

  <p>根据前面的论述，大家可能感觉线程处理非常简单。但必须注意一个问题：共享资源！如果有多个线程同时运行，而且它们试图访问相同的资源，就会遇到一个问题。举个例子来说，两个进程不能将信息同时发送给一台打印机。为解决这个问题，对那些可共享的资源来说（比如打印机），它们在使用期间必须进入锁定状态。所以一个线程可将资源锁定，在完成了它的任务后，再解开（释放）这个锁，使其他线程可以接着使用同样的资源。</p>

  <p>Java的多线程机制已内建到语言中，这使一个可能较复杂的问题变得简单起来。对多线程处理的支持是在对象这一级支持的，所以一个执行线程可表达为一个对象。Java也提供了有限的资源锁定方案。它能锁定任何对象占用的内存（内存实际是多种共享资源的一种），所以同一时间只能有一个线程使用特定的内存空间。为达到这个目的，需要使用synchronized关键字。其他类型的资源必须由程序员明确锁定，这通常要求程序员创建一个对象，用它代表一把锁，所有线程在访问那个资源时都必须检查这把锁。</p>

  <p>1.10 永久性</p>

  <p>创建一个对象后，只要我们需要，它就会一直存在下去。但在程序结束运行时，对象的“生存期”也会宣告结束。尽管这一现象表面上非常合理，但深入追究就会发现，假如在程序停止运行以后，对象也能继续存在，并能保留它的全部信息，那么在某些情况下将是一件非常有价值的事情。下次启动程序时，对象仍然在那里，里面保留的信息仍然是程序上一次运行时的那些信息。当然，可以将信息写入一个文件或者数据库，从而达到相同的效果。但尽管可将所有东西都看作一个对象，如果能将对象声明成“永久性”，并令其为我们照看其他所有细节，无疑也是一件相当方便的事情。</p>

  <p>Java 1.1提供了对“有限永久性”的支持，这意味着我们可将对象简单地保存到磁盘上，以后任何时间都可取回。之所以称它为“有限”的，是由于我们仍然需要明确发出调用，进行对象的保存和取回工作。这些工作不能自动进行。在Java未来的版本中，对“永久性”的支持有望更加全面。</p>

  <p>1.11 Java和因特网</p>

  <p>既然Java不过另一种类型的程序设计语言，大家可能会奇怪它为什么值得如此重视，为什么还有这么多的人认为它是计算机程序设计的一个里程碑呢？如果您来自一个传统的程序设计背景，那么答案在刚开始的时候并不是很明显。Java除了可解决传统的程序设计问题以外，还能解决World Wide Web（万维网）上的编程问题。</p>

  <p>1.11.1 什么是Web？</p>

  <p>Web这个词刚开始显得有些泛泛，似乎“冲浪”、“网上存在”以及“主页”等等都和它拉上了一些关系。甚至还有一种“Internet综合症”的说法，对许多人狂热的上网行为提出了质疑。我们在这里有必要作一些深入的探讨，但在这之前，必须理解客户机／服务器系统的概念，这是充斥着许多令人迷惑的问题的又一个计算领域。</p>

  <p>1. 客户机／服务器计算</p>

  <p>客户机／服务器系统的基本思想是我们能在一个统一的地方集中存放信息资源。一般将数据集中保存在某个数据库中，根据其他人或者机器的请求将信息投递给对方。客户机／服务器概述的一个关键在于信息是“集中存放”的。所以我们能方便地更改信息，然后将修改过的信息发放给信息的消费者。将各种元素集中到一起，信息仓库、用于投递信息的软件以及信息及软件所在的那台机器，它们联合起来便叫作“服务器”（Server）。而对那些驻留在远程机器上的软件，它们需要与服务器通信，取回信息，进行适当的处理，然后在远程机器上显示出来，这些就叫作“客户”（Client）。</p>

  <p>这样看来，客户机／服务器的基本概念并不复杂。这里要注意的一个主要问题是单个服务器需要同时向多个客户提供服务。在这一机制中，通常少不了一套数据库管理系统，使设计人员能将数据布局封装到表格中，以获得最优的使用。除此以外，系统经常允许客户将新信息插入一个服务器。这意味着必须确保客户的新数据不会与其他客户的新数据冲突，或者说需要保证那些数据在加入数据库的时候不会丢失（用数据库的术语来说，这叫作“事务处理”）。客户软件发生了改变之后，它们必须在客户机器上构建、调试以及安装。所有这些会使问题变得比我们一般想象的复杂得多。另外，对多种类型的计算机和操作系统的支持也是一个大问题。最后，性能的问题显得尤为重要：可能会有数百个客户同时向服务器发出请求。所以任何微小的延误都是不能忽视的。为尽可能缓解潜伏的问题，程序员需要谨慎地分散任务的处理负担。一般可以考虑让客户机负担部分处理任务，但有时亦可分派给服务器所在地的其他机器，那些机器亦叫作“中间件”（中间件也用于改进对系统的维护）。</p>

  <p>所以在具体实现的时候，其他人发布信息这样一个简单的概念可能变得异常复杂。有时甚至会使人产生完全无从着手的感觉。客户机／服务器的概念在这时就可以大显身手了。事实上，大约有一半的程序设计活动都可以采用客户机／服务器的结构。这种系统可负责从处理订单及信用卡交易，一直到发布各类数据的方方面面的任务――股票市场、科学研究、政府运作等等。在过去，我们一般为单独的问题采取单独的解决方案；每次都要设计一套新方案。这些方案无论创建还是使用都比较困难，用户每次都要学习和适应新界面。客户机／服务器问题需要从根本上加以变革！</p>

  <p>2. Web是一个巨大的服务器</p>

  <p>Web实际就是一套规模巨大的客户机／服务器系统。但它的情况要复杂一些，因为所有服务器和客户都同时存在于单个网络上面。但我们没必要了解更进一步的细节，因为唯一要关心的就是一次建立同一个服务器的连接，并同它打交道（即使可能要在全世界的范围内搜索正确的服务器）。</p>

  <p>最开始的时候，这是一个简单的单向操作过程。我们向一个服务器发出请求，它向我们回传一个文件，由于本机的浏览器软件（亦即“客户”或“客户程序”）负责解释和格式化，并在我们面前的屏幕上正确地显示出来。但人们不久就不满足于只从一个服务器传递网页。他们希望获得完全的客户机／服务器能力，使客户（程序）也能反馈一些信息到服务器。比如希望对服务器上的数据库进行检索，向服务器添加新信息，或者下一份订单等等（这也提供了比以前的系统更高的安全要求）。在Web的发展过程中，我们可以很清晰地看出这些令人心喜的变化。</p>

  <p>Web浏览器的发展终于迈出了重要的一步：某个信息可在任何类型的计算机上显示出来，毋需任何改动。然而，浏览器仍然显得很原始，在用户迅速增多的要求面前显得有些力不从心。它们的交互能力不够强，而且对服务器和因特网都造成了一定程度的干扰。这是由于每次采取一些要求编程的操作时，必须将信息反馈回服务器，在服务器那一端进行处理。所以完全可能需要等待数秒乃至数分钟的时间才会发现自己刚才拼错了一个单词。由于浏览器只是一个纯粹的查看程序，所以连最简单的计算任务都不能进行（当然在另一方面，它也显得非常安全，因为不能在本机上面执行任何程序，避开了程序错误或者病毒的骚扰）。</p>

  <p>为解决这个问题，人们采取了许多不同的方法。最开始的时候，人们对图形标准进行了改进，使浏览器能显示更好的动画和视频。为解决剩下的问题，唯一的办法就是在客户端（浏览器）内运行程序。这就叫作“客户端编程”，它是对传统的“服务器端编程”的一个非常重要的拓展。</p>

  <p>1.11.2 客户端编程（注释⑧）</p>

  <p>Web最初采用的“服务器－浏览器”方案可提供交互式内容，但这种交互能力完全由服务器提供，为服务器和因特网带来了不小的负担。服务器一般为客户浏览器产生静态网页，由后者简单地解释并显示出来。基本HTML语言提供了简单的数据收集机制：文字输入框、复选框、单选钮、列表以及下拉列表等，另外还有一个按钮，只能由程序规定重新设置表单中的数据，以便回传给服务器。用户提交的信息通过所有Web服务器均能支持的“通用网关接口”（CGI）回传到服务器。包含在提交数据中的文字指示CGI该如何操作。最常见的行动是运行位于服务器的一个程序。那个程序一般保存在一个名为“cgi-bin”的目录中（按下Web页内的一个按钮时，请注意一下浏览器顶部的地址窗，经常都能发现“cgi-bin”的字样）。大多数语言都可用来编制这些程序，但其中最常见的是Perl。这是由于Perl是专为文字的处理及解释而设计的，所以能在任何服务器上安装和使用，无论采用的处理器或操作系统是什么。</p>

  <p>⑧：本节内容改编自某位作者的一篇文章。那篇文章最早出现在位于www.mainspring.com的Mainspring上。本节的采用已征得了对方的同意。</p>

  <p>今天的许多Web站点都严格地建立在CGI的基础上，事实上几乎所有事情都可用CGI做到。唯一的问题就是响应时间。CGI程序的响应取决于需要传送多少数据，以及服务器和因特网两方面的负担有多重（而且CGI程序的启动比较慢）。Web的早期设计者并未预料到当初绰绰有余的带宽很快就变得不够用，这正是大量应用充斥网上造成的结果。例如，此时任何形式的动态图形显示都几乎不能连贯地显示，因为此时必须创建一个GIF文件，再将图形的每种变化从服务器传递给客户。而且大家应该对输入表单上的数据校验有着深刻的体会。原来的方法是我们按下网页上的提交按钮（Submit）；数据回传给服务器；服务器启动一个CGI程序，检查用户输入是否有错；格式化一个HTML页，通知可能遇到的错误，并将这个页回传给我们；随后必须回到原先那个表单页，再输入一遍。这种方法不仅速度非常慢，也显得非常繁琐。</p>

  <p>解决的办法就是客户端的程序设计。运行Web浏览器的大多数机器都拥有足够强的能力，可进行其他大量工作。与此同时，原始的静态HTML方法仍然可以采用，它会一直等到服务器送回下一个页。客户端编程意味着Web浏览器可获得更充分的利用，并可有效改善Web服务器的交互（互动）能力。</p>

  <p>对客户端编程的讨论与常规编程问题的讨论并没有太大的区别。采用的参数肯定是相同的，只是运行的平台不同：Web浏览器就象一个有限的操作系统。无论如何，我们仍然需要编程，仍然会在客户端编程中遇到大量问题，同时也有很多解决的方案。在本节剩下的部分里，我们将对这些问题进行一番概括，并介绍在客户端编程中采取的对策。</p>

  <p>1. 插件</p>

  <p>朝客户端编程迈进的时候，最重要的一个问题就是插件的设计。利用插件，程序员可以方便地为浏览器添加新功能，用户只需下载一些代码，把它们“插入”浏览器的适当位置即可。这些代码的作用是告诉浏览器“从现在开始，你可以进行这些新活动了”（仅需下载这些插入一次）。有些快速和功能强大的行为是通过插件添加到浏览器的。但插件的编写并不是一件简单的任务。在我们构建一个特定的站点时，可能并不希望涉及这方面的工作。对客户端程序设计来说，插件的价值在于它允许专业程序员设计出一种新的语言，并将那种语言添加到浏览器，同时不必经过浏览器原创者的许可。由此可以看出，插件实际是浏览器的一个“后门”，允许创建新的客户端程序设计语言（尽管并非所有语言都是作为插件实现的）。</p>

  <p>2. 脚本编制语言</p>

  <p>插件造成了脚本编制语言的爆炸性增长。通过这种脚本语言，可将用于自己客户端程序的源码直接插入HTML页，而对那种语言进行解释的插件会在HTML页显示的时候自动激活。脚本语言一般都倾向于尽量简化，易于理解。而且由于它们是从属于HTML页的一些简单正文，所以只需向服务器发出对那个页的一次请求，即可非常快地载入。缺点是我们的代码全部暴露在人们面前。另一方面，由于通常不用脚本编制语言做过份复杂的事情，所以这个问题暂且可以放在一边。</p>

  <p>脚本语言真正面向的是特定类型问题的解决，其中主要涉及如何创建更丰富、更具有互动能力的图形用户界面（GUI）。然而，脚本语言也许能解决客户端编程中80％的问题。你碰到的问题可能完全就在那80％里面。而且由于脚本编制语言的宗旨是尽可能地简化与快速，所以在考虑其他更复杂的方案之前（如Java及ActiveX），首先应想一下脚本语言是否可行。</p>

  <p>目前讨论得最多的脚本编制语言包括JavaScript（它与Java没有任何关系；之所以叫那个名字，完全是一种市场策略）、VBScript（同Visual Basic很相似）以及Tcl/Tk（来源于流行的跨平台GUI构造语言）。当然还有其他许多语言，也有许多正在开发中。</p>

  <p>JavaScript也许是目常用的，它得到的支持也最全面。无论NetscapeNavigator，Microsoft Internet Explorer，还是Opera，目前都提供了对JavaScript的支持。除此以外，市面上讲述JavaScript的书籍也要比讲述其他语言的书多得多。有些工具还能利用JavaScript自动产生网页。当然，如果你已经有Visual Basic或者Tcl/Tk的深厚功底，当然用它们要简单得多，起码可以避免学习新语言的烦恼（解决Web方面的问题就已经够让人头痛了）。</p>

  <p>3. Java</p>

  <p>如果说一种脚本编制语言能解决80％的客户端程序设计问题，那么剩下的20％又该怎么办呢？它们属于一些高难度的问题吗？目前最流行的方案就是Java。它不仅是一种功能强大、高度安全、可以跨平台使用以及国际通用的程序设计语言，也是一种具有旺盛生命力的语言。对Java的扩展是不断进行的，提供的语言特性和库能够很好地解决传统语言不能解决的问题，比如多线程操作、数据库访问、连网程序设计以及分布式计算等等。Java通过“程序片”（Applet）巧妙地解决了客户端编程的问题。</p>

  <p>程序片（或“小应用程序”）是一种非常小的程序，只能在Web浏览器中运行。作为Web页的一部分，程序片代码会自动下载回来（这和网页中的图片差不多）。激活程序片后，它会执行一个程序。程序片的一个优点体现在：通过程序片，一旦用户需要客户软件，软件就可从服务器自动下载回来。它们能自动取得客户软件的最新版本，不会出错，也没有重新安装的麻烦。由于Java的设计原理，程序员只需要创建程序的一个版本，那个程序能在几乎所有计算机以及安装了Java解释器的浏览器中运行。由于Java是一种全功能的编程语言，所以在向服务器发出一个请求之前，我们能先在客户端做完尽可能多的工作。例如，再也不必通过因特网传送一个请求表单，再由服务器确定其中是否存在一个拼写或者其他参数错误。大多数数据校验工作均可在客户端完成，没有必要坐在计算机前面焦急地等待服务器的响应。这样一来，不仅速度和响应的灵敏度得到了极大的提高，对网络和服务器造成的负担也可以明显减轻，这对保障因特网的畅通是至关重要的。</p>

  <p>与脚本程序相比，Java程序片的另一个优点是它采用编译好的形式，所以客户端看不到源码。当然在另一方面，反编译Java程序片也并不是件难事，而且代码的隐藏一般并不是个重要的问题。大家要注意另外两个重要的问题。正如本书以前会讲到的那样，编译好的Java程序片可能包含了许多模块，所以要多次“命中”（访问）服务器以便下载（在Java 1.1中，这个问题得到了有效的改善――利用Java压缩档，即JAR文件――它允许设计者将所有必要的模块都封装到一起，供用户统一下载）。在另一方面，脚本程序是作为Web页正文的一部分集成到Web页内的。这种程序一般都非常小，可有效减少对服务器的点击数。另一个因素是学习方面的问题。不管你平时听别人怎么说，Java都不是一种十分容易便可学会的语言。如果你以前是一名Visual Basic程序员，那么转向VBScript会是一种最快捷的方案。由于VBScript可以解决大多数典型的客户机／服务器问题，所以一旦上手，就很难下定决心再去学习Java。如果对脚本编制语言比较熟，那么在转向Java之前，建议先熟悉一下JavaScript或者VBScript，因为它们可能已经能够满足你的需要，不必经历学习Java的艰苦过程。</p>

  <p>4. ActiveX</p>

  <p>在某种程度上，Java的一个有力竞争对手应该是微软的ActiveX，尽管它采用的是完全不同的一套实现机制。ActiveX最早是一种纯Windows的方案。经过一家独立的专业协会的努力，ActiveX现在已具备了跨平台使用的能力。实际上，ActiveX的意思是“假如你的程序同它的工作环境正常连接，它就能进入Web页，并在支持ActiveX的浏览器中运行”（IE固化了对ActiveX的支持，而Netscape需要一个插件）。所以，ActiveX并没有限制我们使用一种特定的语言。比如，假设我们已经是一名有经验的Windows程序员，能熟练地使用象C++、Visual Basic或者BorlandDelphi那样的语言，就能几乎不加任何学习地创建出ActiveX组件。事实上，ActiveX是在我们的Web页中使用“历史遗留”代码的最佳途径。</p>

  <p>5. 安全</p>

  <p>自动下载和通过因特网运行程序听起来就象是一个病毒制造者的梦想。在客户端的编程中，ActiveX带来了最让人头痛的安全问题。点击一个Web站点的时候，可能会随同HTML网页传回任何数量的东西：GIF文件、脚本代码、编译好的Java代码以及ActiveX组件。有些是无害的；GIF文件不会对我们造成任何危害，而脚本编制语言通常在自己可做的事情上有着很大的限制。Java也设计成在一个安全“沙箱”里在它的程序片中运行，这样可防止操作位于沙箱以外的磁盘或者内存区域。</p>

  <p>ActiveX是所有这些里面最让人担心的。用ActiveX编写程序就象编制Windows应用程序――可以做自己想做的任何事情。下载回一个ActiveX组件后，它完全可能对我们磁盘上的文件造成破坏。当然，对那些下载回来并不限于在Web浏览器内部运行的程序，它们同样也可能破坏我们的系统。从BBS下载回来的病毒一直是个大问题，但因特网的速度使得这个问题变得更加复杂。</p>

  <p>目前解决的办法是“数字签名”，代码会得到权威机构的验证，显示出它的作者是谁。这一机制的基础是认为病毒之所以会传播，是由于它的编制者匿名的缘故。所以假如去掉了匿名的因素，所有设计者都不得不为它们的行为负责。这似乎是一个很好的主意，因为它使程序显得更加正规。但我对它能消除恶意因素持怀疑态度，因为假如一个程序便含有Bug，那么同样会造成问题。</p>

  <p>Java通过“沙箱”来防止这些问题的发生。Java解释器内嵌于我们本地的Web浏览器中，在程序片装载时会检查所有有嫌疑的指令。特别地，程序片根本没有权力将文件写进磁盘，或者删除文件（这是病毒最喜欢做的事情之一）。我们通常认为程序片是安全的。而且由于安全对于营建一套可靠的客户机／服务器系统至关重要，所以会给病毒留下漏洞的所有错误都能很快得到修复（浏览器软件实际需要强行遵守这些安全规则；而有些浏览器则允许我们选择不同的安全级别，防止对系统不同程度的访问）。</p>

  <p>大家或许会怀疑这种限制是否会妨碍我们将文件写到本地磁盘。比如，我们有时需要构建一个本地数据库，或将数据保存下来，以便日后离线使用。最早的版本似乎每个人都能在线做任何敏感的事情，但这很快就变得非常不现实（尽管低价“互联网工具”有一天可能会满足大多数用户的需要）。解决的方案是“签了名的程序片”，它用公共密钥加密算法验证程序片确实来自它所声称的地方。当然在通过验证后，签了名的一个程序片仍然可以开始清除你的磁盘。但从理论上说，既然现在能够找到创建人“算帐”，他们一般不会干这种蠢事。Java 1.1为数字签名提供了一个框架，在必要时，可让一个程序片“走”到沙箱的外面来。</p>

  <p>数字签名遗漏了一个重要的问题，那就是人们在因特网上移动的速度。如下载回一个错误百出的程序，而它很不幸地真的干了某些蠢事，需要多久的时间才能发觉这一点呢？这也许是几天，也可能几周之后。发现了之后，又如何追踪当初肇事的程序呢（以及它当时的责任有多大）？</p>

  <p>6. 因特网和内联网</p>

  <p>Web是解决客户机／服务器问题的一种常用方案，所以最好能用相同的技术解决此类问题的一些“子集”，特别是公司内部的传统客户机／服务器问题。对于传统的客户机／服务器模式，我们面临的问题是拥有多种不同类型的客户计算机，而且很难安装新的客户软件。但通过Web浏览器和客户端编程，这两类问题都可得到很好的解决。若一个信息网络局限于一家特定的公司，那么在将Web技术应用于它之后，即可称其为“内联网”（Intranet），以示与国际性的“因特网”（Internet）有别。内联网提供了比因特网更大的安全级别，因为可以物理性地控制对公司内部服务器的使用。说到培训，一般只要人们理解了浏览器的常规概念，就可以非常轻松地掌握网页和程序片之间的差异，所以学习新型系统的开销会大幅度减少。</p>

  <p>安全问题将我们引入客户端编程领域一个似乎是自动形成的分支。若程序是在因特网上运行，由于无从知晓它会在什么平台上运行，所以编程时要特别留意，防范可能出现的编程错误。需作一些跨平台处理，以及适当的安全防范，比如采用某种脚本语言或者Java。</p>

  <p>但假如在内联网中运行，面临的一些制约因素就会发生变化。全部机器均为Intel/Windows平台是件很平常的事情。在内联网中，需要对自己代码的质量负责。而且一旦发现错误，就可以马上改正。除此以外，可能已经有了一些“历史遗留”的代码，并用较传统的客户机／服务器方式使用那些代码。但在进行升级时，每次都要物理性地安装一道客户程序。浪费在升级安装上的时间是转移到浏览器的一项重要原因。使用了浏览器后，升级就变得易如反掌，而且整个过程是透明和自动进行的。如果真的是牵涉到这样的一个内联网中，最明智的方法是采用ActiveX，而非试图采用一种新的语言来改写程序代码。</p>

  <p>面临客户端编程问题令人困惑的一系列解决方案时，最好的方案是先做一次投资／回报分析。请总结出问题的全部制约因素，以及什么才是最快的方案。由于客户端程序设计仍然要编程，所以无论如何都该针对自己的特定情况采取最好的开发途径。这是准备面对程序开发中一些不可避免的问题时，我们可以作出的最佳姿态。</p>

  <p>1.11.3 服务器端编程</p>

  <p>我们的整个讨论都忽略了服务器端编程的问题。如果向服务器发出一个请求，会发生什么事情？大多数时候的请求都是很简单的一个“把这个文件发给我”。浏览器随后会按适当的形式解释这个文件：作为HTML页、一幅图、一个Java程序片、一个脚本程序等等。向服务器发出的较复杂的请求通常涉及到对一个数据库进行操作（事务处理）。其中最常见的就是发出一个数据库检索命令，得到结果后，服务器会把它格式化成HTML页，并作为结果传回来（当然，假如客户通过Java或者某种脚本语言具有了更高的智能，那么原始数据就能在客户端发送和格式化；这样做速度可以更快，也能减轻服务器的负担）。另外，有时需要在数据库中注册自己的名字（比如加入一个组时），或者向服务器发出一份订单，这就涉及到对那个数据库的修改。这类服务器请求必须通过服务器端的一些代码进行，我们称其为“服务器端的编程”。在传统意义上，服务器端编程是用Perl和CGI脚本进行的，但更复杂的系统已经出现。其中包括基于Java的Web服务器，它允许我们用Java进行所有服务器端编程，写出的程序就叫作“小服务程序”（Servlet）。</p>

  <p>1.11.4 一个独立的领域：应用程序</p>

  <p>与Java有关的大多数争论都是与程序片有关的。Java实际是一种常规用途的程序设计语言，可解决任何类型的问题，至少理论上如此。而且正如前面指出的，可以用更有效的方式来解决大多数客户机／服务器问题。如果将视线从程序片身上转开（同时放宽一些限制，比如禁止写盘等），就进入了常规用途的应用程序的广阔领域。这种应用程序可独立运行，毋需浏览器，就象普通的执行程序那样。在这儿，Java的特色并不仅仅反应在它的移植能力，也反映在编程本身上。就象贯穿全书都会讲到的那样，Java提供了许多有用的特性，使我们能在较短的时间里创建出比用从前的程序设计语言更健壮的程序。</p>

  <p>但要注意任何东西都不是十全十美的，我们为此也要付出一些代价。其中最明显的是执行速度放慢了（尽管可对此进行多方面的调整）。和任何语言一样，Java本身也存在一些限制，使得它不十分适合解决某些特殊的编程问题。但不管怎样，Java都是一种正在快速发展的语言。随着每个新版本的发布，它变得越来越可爱，能充分解决的问题也变得越来越多。</p>

  <p>1.12 分析和设计</p>

  <p>面向对象的范式是思考程序设计时一种新的、而且全然不同的方式，许多人最开始都会在如何构造一个项目上皱起了眉头。事实上，我们可以作出一个“好”的设计，它能充分利用OOP提供的所有优点。</p>

  <p>有关OOP分析与设计的书籍大多数都不尽如人意。其中的大多数书都充斥着莫名其妙的话语、笨拙的笔调以及许多听起来似乎很重要的声明（注释⑨）。我认为这种书最好压缩到一章左右的空间，至多写成一本非常薄的书。具有讽剌意味的是，那些特别专注于复杂事物管理的人往往在写一些浅显、明白的书上面大费周章！如果不能说得简单和直接，一定没多少人喜欢看这方面的内容。毕竟，OOP的全部宗旨就是让软件开发的过程变得更加容易。尽管这可能影响了那些喜欢解决复杂问题的人的生计，但为什么不从一开始就把事情弄得简单些呢？因此，希望我能从开始就为大家打下一个良好的基础，尽可能用几个段落来说清楚分析与设计的问题。</p>

  <p>⑨：最好的入门书仍然是Grady Booch的《Object-Oriented Design withApplications，第2版本》，Wiely &amp; Sons于1996年出版。这本书讲得很有深度，而且通俗易懂，尽管他的记号方法对大多数设计来说都显得不必要地复杂。</p>

  <p>1.12.1 不要迷失</p>

  <p>在整个开发过程中，最重要的事情就是：不要将自己迷失！但事实上这种事情很容易发生。大多数方法都设计用来解决最大范围内的问题。当然，也存在一些特别困难的项目，需要作者付出更为艰辛的努力，或者付出更大的代价。但是，大多数项目都是比较“常规”的，所以一般都能作出成功的分析与设计，而且只需用到推荐的一小部分方法。但无论多么有限，某些形式的处理总是有益的，这可使整个项目的开发更加容易，总比直接了当开始编码好！</p>

  <p>也就是说，假如你正在考察一种特殊的方法，其中包含了大量细节，并推荐了许多步骤和文档，那么仍然很难正确判断自己该在何时停止。时刻提醒自己注意以下几个问题：</p>

  <p>(1) 对象是什么？（怎样将自己的项目分割成一系列单独的组件？）</p>

  <p>(2) 它们的接口是什么？（需要将什么消息发给每一个对象？）</p>

  <p>在确定了对象和它们的接口后，便可着手编写一个程序。出于对多方面原因的考虑，可能还需要比这更多的说明及文档，但要求掌握的资料绝对不能比这还少。</p>

  <p>整个过程可划分为四个阶段，阶段0刚刚开始采用某些形式的结构。</p>

  <p>1.12.2 阶段0：拟出一个计划</p>

  <p>第一步是决定在后面的过程中采取哪些步骤。这听起来似乎很简单（事实上，我们这儿说的一切都似乎很简单），但很常见的一种情况是：有些人甚至没有进入阶段1，便忙忙慌慌地开始编写代码。如果你的计划本来就是“直接开始开始编码”，那样做当然也无可非议（若对自己要解决的问题已有很透彻的理解，便可考虑那样做）。但最低程度也应同意自己该有个计划。</p>

  <p>在这个阶段，可能要决定一些必要的附加处理结构。但非常不幸，有些程序员写程序时喜欢随心所欲，他们认为“该完成的时候自然会完成”。这样做刚开始可能不会有什么问题，但我觉得假如能在整个过程中设置几个标志，或者“路标”，将更有益于你集中注意力。这恐怕比单纯地为了“完成工作”而工作好得多。至少，在达到了一个又一个的目标，经过了一个接一个的路标以后，可对自己的进度有清晰的把握，干劲也会相应地提高，不会产生“路遥漫漫无期”的感觉。</p>

  <p>座我刚开始学习故事结构起（我想有一天能写本小说出来），就一直坚持这种做法，感觉就象简单地让文字“流”到纸上。在我写与计算机有关的东西时，发现结构要比小说简单得多，所以不需要考虑太多这方面的问题。但我仍然制订了整个写作的结构，使自己对要写什么做到心中有数。因此，即使你的计划就是直接开始写程序，仍然需要经历以下的阶段，同时向自己提出一些特定的问题。</p>

  <p>1.12.3 阶段1：要制作什么？</p>

  <p>在上一代程序设计中（即“过程化或程序化设计”），这个阶段称为“建立需求分析和系统规格”。当然，那些操作今天已经不再需要了，或者至少改换了形式。大量令人头痛的文档资料已成为历史。但当时的初衷是好的。需求分析的意思是“建立一系列规则，根据它判断任务什么时候完成，以及客户怎样才能满意”。系统规格则表示“这里是一些具体的说明，让你知道程序需要做什么（而不是怎样做）才能满足要求”。需求分析实际就是你和客户之间的一份合约（即使客户就在本公司内部工作，或者是其他对象及系统）。系统规格是对所面临问题的最高级别的一种揭示，我们依据它判断任务是否完成，以及需要花多长的时间。由于这些都需要取得参与者的一致同意，所以我建议尽可能地简化它们――最好采用列表和基本图表的形式――以节省时间。可能还会面临另一些限制，需要把它们扩充成为更大的文档。</p>

  <p>我们特别要注意将重点放在这一阶段的核心问题上，不要纠缠于细枝末节。这个核心问题就是：决定采用什么系统。对这个问题，最有价值的工具就是一个名为“使用条件”的集合。对那些采用“假如……，系统该怎样做？”形式的问题，这便是最有说服力的回答。例如，“假如客户需要提取一张现金支票，但当时又没有这么多的现金储备，那么自动取款机该怎样反应？”对这个问题，“使用条件”可以指示自动取款机在那种“条件”下的正确操作。</p>

  <p>应尽可能总结出自己系统的一套完整的“使用条件”或者“应用场合”。一旦完成这个工作，就相当于摸清了想让系统完成的核心任务。由于将重点放在“使用条件”上，一个很好的效果就是它们总能让你放精力放在最关键的东西上，并防止自己分心于对完成任务关系不大的其他事情上面。也就是说，只要掌握了一套完整的“使用条件”，就可以对自己的系统作出清晰的描述，并转移到下一个阶段。在这一阶段，也有可能无法完全掌握系统日后的各种应用场合，但这也没有关系。只要肯花时间，所有问题都会自然而然暴露出来。不要过份在意系统规格的“完美”，否则也容易产生挫败感和焦燥情绪。</p>

  <p>在这一阶段，最好用几个简单的段落对自己的系统作出描述，然后围绕它们再进行扩充，添加一些“名词”和“动词”。“名词”自然成为对象，而“动词”自然成为要整合到对象接口中的“方法”。只要亲自试着做一做，就会发现这是多么有用的一个工具；有些时候，它能帮助你完成绝大多数的工作。</p>

  <p>尽管仍处在初级阶段，但这时的一些日程安排也可能会非常管用。我们现在对自己要构建的东西应该有了一个较全面的认识，所以可能已经感觉到了它大概会花多长的时间来完成。此时要考虑多方面的因素：如果估计出一个较长的日程，那么公司也许决定不再继续下去；或者一名主管已经估算出了这个项目要花多长的时间，并会试着影响你的估计。但无论如何，最好从一开始就草拟出一份“诚实”的时间表，以后再进行一些暂时难以作出的决策。目前有许多技术可帮助我们计算出准确的日程安排（就象那些预测股票市场起落的技术），但通常最好的方法还是依赖自己的经验和直觉（不要忘记，直觉也要建立在经验上）。感觉一下大概需要花多长的时间，然后将这个时间加倍，再加上10％。你的感觉可能是正确的；“也许”能在那个时间里完成。但“加倍”使那个时间更加充裕，“10％”的时间则用于进行最后的推敲和深化。但同时也要对此向上级主管作出适当的解释，无论对方有什么抱怨和修改，只要明确地告诉他们：这样的一个日程安排，只是我的一个估计！</p>

  <p>1.12.4 阶段2：如何构建？</p>

  <p>在这一阶段，必须拿出一套设计方案，并解释其中包含的各类对象在外观上是什么样子，以及相互间是如何沟通的。此时可考虑采用一种特殊的图表工具：“统一建模语言”（UML）。请到http://www.rational.com去下载一份UML规格书。作为第1阶段中的描述工具，UML也是很有帮助的。此外，还可用它在第2阶段中处理一些图表（如流程图）。当然并非一定要使用UML，但它对你会很有帮助，特别是在希望描绘一张详尽的图表，让许多人在一起研究的时候。除UML外，还可选择对对象以及它们的接口进行文字化描述（就象我在《Thinking in C++》里说的那样，但这种方法非常原始，发挥的作用亦较有限。</p>

  <p>我曾有一次非常成功的咨询经历，那时涉及到一小组人的初始设计。他们以前还没有构建过OOP（面向对象程序设计）项目，将对象画在白板上面。我们谈到各对象相互间该如何沟通（通信），并删除了其中的一部分，以及替换了另一部分对象。这个小组（他们知道这个项目的目的是什么）实际上已经制订出了设计方案；他们自己“拥有”了设计，而不是让设计自然而然地显露出来。我在那里做的事情就是对设计进行指导，提出一些适当的问题，尝试作出一些假设，并从小组中得到反馈，以便修改那些假设。这个过程中最美妙的事情就是整个小组并不是通过学习一些抽象的例子来进行面向对象的设计，而是通过实践一个真正的设计来掌握OOP的窍门，而那个设计正是他们当时手上的工作！</p>

  <p>作出了对对象以及它们的接口的说明后，就完成了第2阶段的工作。当然，这些工作可能并不完全。有些工作可能要等到进入阶段3才能得知。但这已经足够了。我们真正需要关心的是最终找出所有的对象。能早些发现当然好，但OOP提供了足够完美的结构，以后再找出它们也不迟。</p>

  <p>1.12.5 阶段3：开始创建</p>

  <p>读这本书的可能是程序员，现在进入的正是你可能最感兴趣的阶段。由于手头上有一个计划――无论它有多么简要，而且在正式编码前掌握了正确的设计结构，所以会发现接下去的工作比一开始就埋头写程序要简单得多。而这正是我们想达到的目的。让代码做到我们想做的事情，这是所有程序项目最终的目标。但切不要急功冒进，否则只有得不偿失。根据我的经验，最后先拿出一套较为全面的方案，使其尽可能设想周全，能满足尽可能多的要求。给我的感觉，编程更象一门艺术，不能只是作为技术活来看待。所有付出最终都会得到回报。作为真正的程序员，这并非可有可无的一种素质。全面的思考、周密的准备、良好的构造不仅使程序更易构建与调试，也使其更易理解和维护，而那正是一套软件赢利的必要条件。</p>

  <p>构建好系统，并令其运行起来后，必须进行实际检验，以前做的那些需求分析和系统规格便可派上用场了。全面地考察自己的程序，确定提出的所有要求均已满足。现在一切似乎都该结束了？是吗？</p>

  <p>1.12.6 阶段4：校订</p>

  <p>事实上，整个开发周期还没有结束，现在进入的是传统意义上称为“维护”的一个阶段。“维护”是一个比较暧昧的称呼，可用它表示从“保持它按设想的轨道运行”、“加入客户从前忘了声明的功能”或者更传统的“除掉暴露出来的一切臭虫”等等意思。所以大家对“维护”这个词产生了许多误解，有的人认为：凡是需要“维护”的东西，必定不是好的，或者是有缺陷的！因为这个词说明你实际构建的是一个非常“原始”的程序，以后需要频繁地作出改动、添加新的代码或者防止它的落后、退化等。因此，我们需要用一个更合理的词语来称呼以后需要继续的工作。</p>

  <p>这个词便是“校订”。换言之，“你第一次做的东西并不完善，所以需为自己留下一个深入学习、认知的空间，再回过头去作一些改变”。对于要解决的问题，随着对它的学习和了解愈加深入，可能需要作出大量改动。进行这些工作的一个动力是随着不断的改革优化，终于能够从自己的努力中得到回报，无论这需要经历一个较短还是较长的时期。</p>

  <p>什么时候才叫“达到理想的状态”呢？这并不仅仅意味着程序必须按要求的那样工作，并能适应各种指定的“使用条件”，它也意味着代码的内部结构应当尽善尽美。至少，我们应能感觉出整个结构都能良好地协调运作。没有笨拙的语法，没有臃肿的对象，也没有一些华而不实的东西。除此以外，必须保证程序结构有很强的生命力。由于多方面的原因，以后对程序的改动是必不可少。但必须确定改动能够方便和清楚地进行。这里没有花巧可言。不仅需要理解自己构建的是什么，也要理解程序如何不断地进化。幸运的是，面向对象的程序设计语言特别适合进行这类连续作出的修改――由对象建立起来的边界可有效保证结构的整体性，并能防范对无关对象进行的无谓干扰、破坏。也可以对自己的程序作一些看似激烈的大变动，同时不会破坏程序的整体性，不会波及到其他代码。事实上，对“校订”的支持是OOP非常重要的一个特点。</p>

  <p>通过校订，可创建出至少接近自己设想的东西。然后从整体上观察自己的作品，把它与自己的要求比较，看看还短缺什么。然后就可以从容地回过头去，对程序中不恰当的部分进行重新设计和重新实现（注释⑩）。在最终得到一套恰当的方案之前，可能需要解决一些不能回避的问题，或者至少解决问题的一个方面。而且一般要多“校订”几次才行（“设计范式”在这里可起到很大的帮助作用。有关它的讨论，请参考本书第16章）。</p>

  <p>构建一套系统时，“校订”几乎是不可避免的。我们需要不断地对比自己的需求，了解系统是否自己实际所需要的。有时只有实际看到系统，才能意识到自己需要解决一个不同的问题。若认为这种形式的校订必然会发生，那么最好尽快拿出自己的第一个版本，检查它是否自己希望的，使自己的思想不断趋向成熟。</p>

  <p>反复的“校订”同“递增开发”有关密不可分的关系。递增开发意味着先从系统的核心入手，将其作为一个框架实现，以后要在这个框架的基础上逐渐建立起系统剩余的部分。随后，将准备提供的各种功能（特性）一个接一个地加入其中。这里最考验技巧的是架设起一个能方便扩充所有目标特性的一个框架（对这个问题，大家可参考第16章的论述）。这样做的好处在于一旦令核心框架运作起来，要加入的每一项特性就象它自身内的一个小项目，而非大项目的一部分。此外，开发或维护阶段合成的新特性可以更方便地加入。OOP之所以提供了对递增开发的支持，是由于假如程序设计得好，每一次递增都可以成为完善的对象或者对象组。</p>

  <p>⑩：这有点类似“快速造型”。此时应着眼于建立一个简单、明了的版本，使自己能对系统有个清楚的把握。再把这个原型扔掉，并正式地构建一个。快速造型最麻烦的一种情况就是人们不将原型扔掉，而是直接在它的基础上建造。如果再加上程序化设计中“结构”的缺乏，就会导致一个混乱的系统，致使维护成本增加。</p>

  <p>1.12.7 计划的回报</p>

  <p>如果没有仔细拟定的设计图，当然不可能建起一所房子。如建立的是一所狗舍，尽管设计图可以不必那么详尽，但仍然需要一些草图，以做到心中有数。软件开发则完全不同，它的“设计图”（计划）必须详尽而完备。在很长的一段时间里，人们在他们的开发过程中并没有太多的结构，但那些大型项目很容易就会遭致失败。通过不断的摸索，人们掌握了数量众多的结构和详细资料。但它们的使用却使人提心吊胆在意――似乎需要把自己的大多数时间花在编写文档上，而没有多少时间来编程（经常如此）。我希望这里为大家讲述的一切能提供一条折衷的道路。需要采取一种最适合自己需要（以及习惯）的方法。不管制订出的计划有多么小，但与完全没有计划相比，一些形式的计划会极大改善你的项目。请记住：根据估计，没有计划的50％以上的项目都会失败！</p>

  <p>1.13 Java还是C++？</p>

  <p>Java特别象C++；由此很自然地会得出一个结论：C++似乎会被Java取代。但我对这个逻辑存有一些疑问。无论如何，C++仍有一些特性是Java没有的。而且尽管已有大量保证，声称Java有一天会达到或超过C++的速度。但这个突破迄今仍未实现（尽管Java的速度确实在稳步提高，但仍未达到C++的速度）。此外，许多领域都存在为数众多的C++爱好者，所以我并不认为那种语言很快就会被另一种语言替代（爱好者的力量是容忽视的。比如在我主持的一次“中／高级Java研讨会”上，Allen Holub声称两种最常用的语言是Rexx和COBOL）。</p>

  <p>我感觉Java强大之处反映在与C++稍有不同的领域。C++是一种绝对不会试图迎合某个模子的语言。特别是它的形式可以变化多端，以解决不同类型的问题。这主要反映在象Microsoft Visual C++和Borland C++ Builder（我最喜欢这个）那样的工具身上。它们将库、组件模型以及代码生成工具等合成到一起，以开发视窗化的末端用户应用（用于Microsoft Windows操作系统）。但在另一方面，Windows开发人员最常用的是什么呢？是微软的Visual Basic（VB）。当然，我们在这儿暂且不提VB的语法极易使人迷惑的事实――即使一个只有几页长度的程序，产生的代码也十分难于管理。从语言设计的角度看，尽管VB是那样成功和流行，但仍然存在不少的缺点。最好能够同时拥有VB那样的强大功能和易用性，同时不要产生难于管理的代码。而这正是Java最吸引人的地方：作为“下一代的VB”。无论你听到这种主张后有什么感觉，请无论如何都仔细想一想：人们对Java做了大量的工作，使它能方便程序员解决应用级问题（如连网和跨平台UI等），所以它在本质上允许人们创建非常大型和灵活的代码主体。同时，考虑到Java还拥有我迄今为止尚未在其他任何一种语言里见到的最“健壮”的类型检查及错误控制系统，所以Java确实能大大提高我们的编程效率。这一点是勿庸置疑的！</p>

  <p>但对于自己某个特定的项目，真的可以不假思索地将C++换成Java吗？除了Web程序片，还有两个问题需要考虑。首先，假如要使用大量现有的库（这样肯定可以提高不少的效率），或者已经有了一个坚实的C或C++代码库，那么换成Java后，反映会阻碍开发进度，而不是加快它的速度。但若想从头开始构建自己的所有代码，那么Java的简单易用就能有效地缩短开发时间。</p>

  <p>最大的问题是速度。在原始的Java解释器中，解释过的Java会比C慢上20到50倍。尽管经过长时间的发展，这个速度有一定程度的提高，但和C比起来仍然很悬殊。计算机最注重的就是速度；假如在一台计算机上不能明显较快地干活，那么还不如用手做（有人建议在开发期间使用Java，以缩短开发时间。然后用一个工具和支撑库将代码转换成C++，这样可获得更快的执行速度）。</p>

  <p>为使Java适用于大多数Web开发项目，关键在于速度上的改善。此时要用到人们称为“刚好及时”（Just-In Time，或JIT）的编译器，甚至考虑更低级的代码编译器（写作本书时，也有两款问世）。当然，低级代码编译器会使编译好的程序不能跨平台执行，但同时也带来了速度上的提升。这个速度甚至接近C和C++。而且Java中的程序交叉编译应当比C和C++中简单得多（理论上只需重编译即可，但实际仍较难实现；其他语言也曾作出类似的保证）。</p>

  <p>在本书附录，大家可找到与Java／C++比较．对Java现状的观察以及编码规则有关的内容。英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>-------------------------------------------------</p>

  <p>TXT书库 www.16txt.com</p>

  <p>-------------------------------------------------第2章 一切都是对象</p>

  <p>“尽管以C++为基础，但Java是一种更纯粹的面向对象程序设计语言”。</p>

  <p>无论C++还是Java都属于杂合语言。但在Java中，设计者觉得这种杂合并不象在C++里那么重要。杂合语言允许采用多种编程风格；之所以说C++是一种杂合语言，是因为它支持与C语言的向后兼容能力。由于C++是C的一个超集，所以包含的许多特性都是后者不具备的，这些特性使C++在某些地方显得过于复杂。</p>

  <p>Java语言首先便假定了我们只希望进行面向对象的程序设计。也就是说，正式用它设计之前，必须先将自己的思想转入一个面向对象的世界（除非早已习惯了这个世界的思维方式）。只有做好这个准备工作，与其他OOP语言相比，才能体会到Java的易学易用。在本章，我们将探讨Java程序的基本组件，并体会为什么说Java乃至Java程序内的一切都是对象。</p>

  <p>2.1 用句柄操纵对象</p>

  <p>每种编程语言都有自己的数据处理方式。有些时候，程序员必须时刻留意准备处理的是什么类型。您曾利用一些特殊语法直接操作过对象，或处理过一些间接表示的对象吗（C或C++里的指针）？</p>

  <p>所有这些在Java里都得到了简化，任何东西都可看作对象。因此，我们可采用一种统一的语法，任何地方均可照搬不误。但要注意，尽管将一切都“看作”对象，但操纵的标识符实际是指向一个对象的“句柄”（Handle）。在其他Java参考书里，还可看到有的人将其称作一个“引用”，甚至一个“指针”。可将这一情形想象成用遥控板（句柄）操纵电视机（对象）。只要握住这个遥控板，就相当于掌握了与电视机连接的通道。但一旦需要“换频道”或者“关小声音”，我们实际操纵的是遥控板（句柄），再由遥控板自己操纵电视机（对象）。如果要在房间里四处走走，并想保持对电视机的控制，那么手上拿着的是遥控板，而非电视机。</p>

  <p>此外，即使没有电视机，遥控板亦可独立存在。也就是说，只是由于拥有一个句柄，并不表示必须有一个对象同它连接。所以如果想容纳一个词或句子，可创建一个String句柄：</p>

  <p>String s;</p>

  <p>但这里创建的只是句柄，并不是对象。若此时向s发送一条消息，就会获得一个错误（运行期）。这是由于s实际并未与任何东西连接（即“没有电视机”）。因此，一种更安全的做法是：创建一个句柄时，记住无论如何都进行初始化：</p>

  <p>String s = "asdf";</p>

  <p>然而，这里采用的是一种特殊类型：字串可用加引号的文字初始化。通常，必须为对象使用一种更通用的初始化类型。</p>

  <p>2.2 所有对象都必须创建</p>

  <p>创建句柄时，我们希望它同一个新对象连接。通常用new关键字达到这一目的。new的意思是：“把我变成这些对象的一种新类型”。所以在上面的例子中，可以说：</p>

  <p>String s = new String("asdf");</p>

  <p>它不仅指出“将我变成一个新字串”，也通过提供一个初始字串，指出了“如何生成这个新字串”。</p>

  <p>当然，字串（String）并非唯一的类型。Java配套提供了数量众多的现成类型。对我们来讲，最重要的就是记住能自行创建类型。事实上，这应是Java程序设计的一项基本操作，是继续本书后余部分学习的基础。</p>

  <p>2.2.1 保存到什么地方</p>

  <p>程序运行时，我们最好对数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据：</p>

  <p>(1) 寄存器。这是最快的保存区域，因为它位于和其他所有保存方式不同的地方：处理器内部。然而，寄存器的数量十分有限，所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权，也不可能在自己的程序里找到寄存器存在的任何踪迹。</p>

  <p>(2) 堆栈。驻留于常规RAM（随机访问存储器）区域，但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移，会创建新的内存；若向上移，则会释放那些内存。这是一种特别快、特别有效的数据保存方式，仅次于寄存器。创建程序时，Java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码，以便向上和向下移动指针。这一限制无疑影响了程序的灵活性，所以尽管有些Java数据要保存在堆栈里――特别是对象句柄，但Java对象并不放到其中。</p>

  <p>(3) 堆。一种常规用途的内存池（也在RAM区域），其中保存了Java对象。和堆栈不同，“内存堆”或“堆”（Heap）最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间，也不必知道存储的数据要在堆里停留多长的时间。因此，用堆保存数据时会得到更大的灵活性。要求创建一个对象时，只需用new命令编制相关的代码即可。执行这些代码时，会在堆里自动进行数据的保存。当然，为达到这种灵活性，必然会付出一定的代价：在堆里分配存储空间时会花掉更长的时间！</p>

  <p>(4) 静态存储。这儿的“静态”（Static）是指“位于固定位置”（尽管也在RAM里）。程序运行期间，静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java对象本身永远都不会置入静态存储空间。</p>

  <p>(5) 常数存储。常数值通常直接置于程序代码内部。这样做是安全的，因为它们永远都不会改变。有的常数需要严格地保护，所以可考虑将它们置入只读存储器（ROM）。</p>

  <p>(6) 非RAM存储。若数据完全独立于一个程序之外，则程序不运行时仍可存在，并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象，对象会变成字节流，通常会发给另一台机器。而对于固定对象，对象保存在磁盘中。即使程序中止运行，它们仍可保持自己的状态不变。对于这些类型的数据存储，一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要，甚至能将它们恢复成普通的、基于RAM的对象。Java 1.1提供了对Lightweight persistence的支持。未来的版本甚至可能提供更完整的方案。</p>

  <p>2.2.2 特殊情况：主要类型</p>

  <p>有一系列类需特别对待；可将它们想象成“基本”、“主要”或者“主”（Primitive）类型，进行程序设计时要频繁用到它们。之所以要特别对待，是由于用new创建对象（特别是小的、简单的变量）并不是非常有效，因为new将对象置于“堆”里。对于这些类型，Java采纳了与C和C++相同的方法。也就是说，不是用new创建变量，而是创建一个并非句柄的“自动”变量。这个变量容纳了具体的值，并置于堆栈中，能够更高效地存取。</p>

  <p>Java决定了每种主要类型的大小。就象在大多数语言里那样，这些大小并不随着机器结构的变化而变化。这种大小的不可更改正是Java程序具有很强移植能力的原因之一。</p>

  <p>主类型</p>

  <p>大小</p>

  <p>最小值</p>

  <p>最大值</p>

  <p>封装器类型</p>

  <p>boolean</p>

  <p>1-bit</p>

  <p>Boolean</p>

  <p>char</p>

  <p>16-bit</p>

  <p>Unicode 0</p>

  <p>Unicode 216- 1</p>

  <p>Character</p>

  <p>byte</p>

  <p>8-bit</p>

  <p>-128</p>

  <p>+127</p>

  <p>Byte[11]</p>

  <p>short</p>

  <p>16-bit</p>

  <p>-215</p>

  <p>+215 1</p>

  <p>Short1</p>

  <p>int</p>

  <p>32-bit</p>

  <p>-231</p>

  <p>+231 1</p>

  <p>Integer</p>

  <p>long</p>

  <p>64-bit</p>

  <p>-263</p>

  <p>+263 1</p>

  <p>Long</p>

  <p>float</p>

  <p>32-bit</p>

  <p>IEEE754</p>

  <p>IEEE754</p>

  <p>Float</p>

  <p>double</p>

  <p>64-bit</p>

  <p>IEEE754</p>

  <p>IEEE754</p>

  <p>Double</p>

  <p>void</p>

  <p>Void1①：到Java 1.1才有，1.0版没有。</p>

  <p>数值类型全都是有符号（正负号）的，所以不必费劲寻找没有符号的类型。</p>

  <p>主数据类型也拥有自己的“封装器”（wrapper）类。这意味着假如想让堆内一个非主要对象表示那个主类型，就要使用对应的封装器。例如：</p>

  <p>char c = 'x';</p>

  <p>Character C = new Character('c');</p>

  <p>也可以直接使用：</p>

  <p>Character C = new Character('x');</p>

  <p>这样做的原因将在以后的章节里解释。</p>

  <p>1. 高精度数字</p>

  <p>Java 1.1增加了两个类，用于进行高精度的计算：BigInteger和BigDecimal。尽管它们大致可以划分为“封装器”类型，但两者都没有对应的“主类型”。</p>

  <p>这两个类都有自己特殊的“方法”，对应于我们针对主类型执行的操作。也就是说，能对int或float做的事情，对BigInteger和BigDecimal一样可以做。只是必须使用方法调用，不能使用运算符。此外，由于牵涉更多，所以运算速度会慢一些。我们牺牲了速度，但换来了精度。</p>

  <p>BigInteger支持任意精度的整数。也就是说，我们可精确表示任意大小的整数值，同时在运算过程中不会丢失任何信息。</p>

  <p>BigDecimal支持任意精度的定点数字。例如，可用它进行精确的币值计算。</p>

  <p>至于调用这两个类时可选用的构建器和方法，请自行参考联机帮助文档。</p>

  <p>2.2.3 Java的数组</p>

  <p>几乎所有程序设计语言都支持数组。在C和C++里使用数组是非常危险的，因为那些数组只是内存块。若程序访问自己内存块以外的数组，或者在初始化之前使用内存（属于常规编程错误），会产生不可预测的后果（注释②）。</p>

  <p>②：在C++里，应尽量不要使用数组，换用标准模板库（Standard TemplateLibrary）里更安全的容器。</p>

  <p>Java的一项主要设计目标就是安全性。所以在C和C++里困扰程序员的许多问题都未在Java里重复。一个Java可以保证被初始化，而且不可在它的范围之外访问。由于系统自动进行范围检查，所以必然要付出一些代价：针对每个数组，以及在运行期间对索引的校验，都会造成少量的内存开销。但由此换回的是更高的安全性，以及更高的工作效率。为此付出少许代价是值得的。</p>

  <p>创建对象数组时，实际创建的是一个句柄数组。而且每个句柄都会自动初始化成一个特殊值，并带有自己的关键字：null（空）。一旦Java看到null，就知道该句柄并未指向一个对象。正式使用前，必须为每个句柄都分配一个对象。若试图使用依然为null的一个句柄，就会在运行期报告问题。因此，典型的数组错误在Java里就得到了避免。</p>

  <p>也可以创建主类型数组。同样地，编译器能够担保对它的初始化，因为会将那个数组的内存划分成零。</p>

  <p>数组问题将在以后的章节里详细讨论。</p>

  <p>2.3 绝对不要清除对象</p>

  <p>在大多数程序设计语言中，变量的“存在时间”（Lifetime）一直是程序员需要着重考虑的问题。变量应持续多长的时间？如果想清除它，那么何时进行？在变量存在时间上纠缠不清会造成大量的程序错误。在下面的小节里，将阐示Java如何帮助我们完成所有清除工作，从而极大了简化了这个问题。</p>

  <p>2.3.1 作用域</p>

  <p>大多数程序设计语言都提供了“作用域”（Scope）的概念。对于在作用域里定义的名字，作用域同时决定了它的“可见性”以及“存在时间”。在C，C++和Java里，作用域是由花括号的位置决定的。参考下面这个例子：{</p>

  <p>int x = 12;</p>

  <p>/* only x available */</p>

  <p>{</p>

  <p>int q = 96;</p>

  <p>/* both x &amp; q available */</p>

  <p>}</p>

  <p>/* only x available */</p>

  <p>/* q “out of scope” */</p>

  <p>}</p>

  <p>作为在作用域里定义的一个变量，它只有在那个作用域结束之前才可使用。</p>

  <p>在上面的例子中，缩进排版使Java代码更易辨读。由于Java是一种形式自由的语言，所以额外的空格、制表位以及回车都不会对结果程序造成影响。</p>

  <p>注意尽管在C和C++里是合法的，但在Java里不能象下面这样书写代码：</p>

  <p>{</p>

  <p>int x = 12;</p>

  <p>{</p>

  <p>int x = 96; /* illegal */</p>

  <p>}</p>

  <p>}</p>

  <p>编译器会认为变量x已被定义。所以C和C++能将一个变量“隐藏”在一个更大的作用域里。但这种做法在Java里是不允许的，因为Java的设计者认为这样做使程序产生了混淆。</p>

  <p>2.3.2 对象的作用域</p>

  <p>Java对象不具备与主类型一样的存在时间。用new关键字创建一个Java对象的时候，它会超出作用域的范围之外。所以假若使用下面这段代码：</p>

  <p>{</p>

  <p>String s = new String("a string");</p>

  <p>} /* 作用域的终点 */</p>

  <p>那么句柄s会在作用域的终点处消失。然而，s指向的String对象依然占据着内存空间。在上面这段代码里，我们没有办法访问对象，因为指向它的唯一一个句柄已超出了作用域的边界。在后面的章节里，大家还会继续学习如何在程序运行期间传递和复制对象句柄。</p>

  <p>这样造成的结果便是：对于用new创建的对象，只要我们愿意，它们就会一直保留下去。这个编程问题在C和C++里特别突出。看来在C++里遇到的麻烦最大：由于不能从语言获得任何帮助，所以在需要对象的时候，根本无法确定它们是否可用。而且更麻烦的是，在C++里，一旦工作完成，必须保证将对象清除。</p>

  <p>这样便带来了一个有趣的问题。假如Java让对象依然故我，怎样才能防止它们大量充斥内存，并最终造成程序的“凝固”呢。在C++里，这个问题最令程序员头痛。但Java以后，情况却发生了改观。Java有一个特别的“垃圾收集器”，它会查找用new创建的所有对象，并辨别其中哪些不再被引用。随后，它会自动释放由那些闲置对象占据的内存，以便能由新对象使用。这意味着我们根本不必操心内存的回收问题。只需简单地创建对象，一旦不再需要它们，它们就会自动离去。这样做可防止在C++里很常见的一个编程问题：由于程序员忘记释放内存造成的“内存溢出”。</p>

  <p>2.4 新建数据类型：类</p>

  <p>如果说一切东西都是对象，那么用什么决定一个“类”（Class）的外观与行为呢？换句话说，是什么建立起了一个对象的“类型”（Type）呢？大家可能猜想有一个名为“type”的关键字。但从历史看来，大多数面向对象的语言都用关键字“class”表达这样一个意思：“我准备告诉你对象一种新类型的外观”。class关键字太常用了，以至于本书许多地方并没有用粗体字或双引号加以强调。在这个关键字的后面，应该跟随新数据类型的名称。例如：</p>

  <p>class ATypeName {/*类主体置于这里}</p>

  <p>这样就引入了一种新类型，接下来便可用new创建这种类型的一个新对象：</p>

  <p>ATypeName a = new ATypeName();</p>

  <p>在ATypeName里，类主体只由一</p>

  <div class="mbppagebreak"></div>
</body>
</html>
